<?php
namespace app\mxty\logic;

use think\facade\Request;
use think\facade\Cache;
use \DateTime;

use app\mxty\model\Arena as ArenaModel;
use app\mxty\model\BookingFragment as BookingFragmentModel;
use app\mxty\model\ArenaPartTimeFragment as ArenaPartTimeFragmentModel;
use app\mxty\model\Booking as BookingModel;
use app\mxty\model\BookingReferee as BookingRefereeModel;
use app\api\model\Wxapp as WxappModel;
use app\mxty\model\Coach as CoachModel;
use app\api\model\User as UserModel;

use app\api\service\User as UserService;
use app\api\service\passport\Login as LoginService;
use app\mxty\logic\Referee as RefereeLogic;

use app\common\library\wechat\WxPay;

use app\common\enum\OrderType as OrderTypeEnum;
use app\common\enum\order\PayStatus as OrderPayStatusEnum;

class Arena
{
    // 判断单个场次（指定日期和时段下）是否有裁判已被预订
    public function getBookingReferees($date, $timeFragment)
    {
        // 已被预订的裁判集合
        $refereesBooked = [];
        // 查询当天已付款的订场
        $bookingList = (new BookingModel)->getBookingByDate($date);

        // 遍历这些订单
        foreach($bookingList as $booking) {
            $bookingFragments = $booking->fragments;

            // 遍历订单的所有时段
            foreach($bookingFragments as $bookingFragment) {
                // 已预订的时间段
                $bookingTime = $bookingFragment->time_fragment;
                // 比较指定时段和已预订时段是否重合
                $isOverlap = $this->compareTimeFragment($bookingTime, $timeFragment);
                // 只要有一个时段有重合，则该已预订时段对应的订单下所有裁判都不能选择
                if($isOverlap) {
                    $bookingId = $booking->id;
                    // 查询该订场ID对应的裁判
                    $referees = BookingRefereeModel::getRefereeByBookingId($bookingId)->toArray();
                    foreach($referees as $referee) {
                        array_push($refereesBooked, $referee);
                    }
                    break;
                }
            }
        }

        // 裁判集合去重
        if(count($refereesBooked) > 1) {
            $refereesBooked = assoc_unique($refereesBooked, 'referee_id');
        }

        return $refereesBooked;
    }

    // 比较两个时间段是否有重合
    public function compareTimeFragment($bookingTime, $timeFragment)
    {
        $booking = explode('-', $bookingTime);
        $time = explode('-', $timeFragment);

        return $booking[1] >= $time[0] && $booking[0] <= $time[1];
    }

    // 获取指定场地下所有半场的空闲场次
    public function getPartsInfoByAreanId($date, $arenaId)
    {
        $info = (new ArenaModel())->getIdleAreanInfoById($date, $arenaId);
        // 获取该场地的最少选择限制和最多选择限制
        $minimum = $info->minimum;
        $maximum = $info->maximum;

        // 记录场地最新的记录操作时间
        $update_time = 0;
        // 检查该场地是否有设置半场
        if($info->parts->isEmpty()) {
            throwError('该场地没有设置半场');
        }
        // 遍历该场地下所有半场，获取场次的最新状态和价格
        $parts = array_map(function ($part) use (
            &$update_time,
            &$minimum,
            &$maximum
        ) {
            // 记录该半场的连续选择限制
            $continuity = $part['continuity'];

            // 遍历该半场下所有场次。$session为单个场次，$session['bookings']为该场次在指定日期内所有的预订记录（已在上面查询里限定了日期）
            $sessions = array_map(function ($session) use (
                &$update_time,
                &$minimum,
                &$maximum,
                &$continuity
            ) {
                // 获取属于前台用户预订且已付款的记录
                $bookings_user = array_filter($session['bookings'], function (
                    $val
                ) {
                    return $val['book'] == 1 &&
                        $val['pay_status'] == OrderPayStatusEnum::SUCCESS;
                });

                // 获取属于管理员操作的记录
                $bookings_admin = array_filter($session['bookings'], function (
                    $val
                ) {
                    return $val['book'] == 0;
                });

                // 若该日期下该场次有后台管理员操作记录
                if (!empty($bookings_admin)) {
                    $latest_bookings_admin = $bookings_admin[0];
                    $status = $latest_bookings_admin['status'];
                    $price = $latest_bookings_admin['price'];
                    // 修改该半场的连续选择设置（当前是该半场下所有场次中最后的操作记录，所以也是有关该半场的所有操作记录中最新的）
                    $continuity = $latest_bookings_admin['continuity'];

                    // 一个场地会有多个半场，每个半场会有多个场次，遍历半场时取出该半场的最新操作时间作为记录(在遍历该半场所有场次时取出操作时间为最新的那条)
                    if ($latest_bookings_admin['update_time'] > $update_time) {
                        $update_time = $latest_bookings_admin['update_time'];
                        $minimum = $latest_bookings_admin['minimum'];
                        $maximum = $latest_bookings_admin['maximum'];
                    }
                }

                // 若该日期下该场次已有前台用户预订且已付款，则设置该日该场次的状态为关闭
                if (!empty($bookings_user)) {
                    $status = $bookings_user[0]['status'];
                }

                // 返回场次信息
                return [
                    'part_time_fragment_id' => $session['id'],
                    'time_fragment_id' => $session['time_fragment_id'],
                    'fragment' => "{$session['fragment']['start']}-{$session['fragment']['end']}",
                    'status' => $status ?? $session['status'],
                    'price' => $price ?? $session['price'],
                    'sort' => $session['fragment']['sort'],
                ];
            },
            $part['sessions']);
            // 按照 sort 升序排序
            array_multisort(
                array_column($sessions, 'sort'),
                SORT_ASC,
                $sessions
            );

            // 返回半场信息
            return [
                'arena_part_id' => $part['arena_part_id'],
                'name' => $part['name'],
                'continuity' => $continuity,
                'sessions' => $sessions,
            ];
        },
        $info->parts->toArray());

        // 场地信息数组
        $data = [
            'arena_id' => $info->arena_id,
            'name' => $info->name,
            'minimum' => $minimum,
            'maximum' => $maximum,
            'parts' => $parts,
        ];

        return $data;
    }

    // 获取所有场地的空闲场次数
    public function getIdleArenaInfo($date)
    {
        $list = (new ArenaModel())->getAllIdleAreans($date);

        $arenas = array_map(function ($arena) {
            $free_count = 0;
            // 遍历该场地下所有半场
            foreach ($arena['parts'] as $part) {
                // 遍历该半场下所有场次，判断该场次的状态是否为0（空闲）
                foreach ($part['sessions'] as $session) {
                    $isFree = $session['status'] === 0;
                    // 若该场次存在预订记录，判断预订记录中该场次的状态是否已变为关闭（已预约）
                    if (!empty($session['bookings'])) {
                        // 过滤这些预订记录中为前台预订但未付款的记录
                        $bookings = array_filter(
                            $session['bookings'],
                            function ($val) {
                                if ($val['book'] == 1) {
                                    return $val['pay_status'] ==
                                        OrderPayStatusEnum::SUCCESS;
                                } else {
                                    return true;
                                }
                            }
                        );
                        // 如果预订记录为空或者状态为空闲, 只取记录列表中的第一项为准（已按最新时间排序）
                        $isFree =
                            empty($bookings) || $bookings[0]['status'] === 0;
                    }

                    $isFree && $free_count++;
                }
            }

            return [
                'arena_id' => $arena['arena_id'],
                'name' => $arena['name'],
                'sort' => $arena['sort'],
                'free_count' => $free_count,
            ];
        }, $list->toArray());

        return $arenas;
    }

    public function hasSameRefere($date, $collection, $refereeIds)
    {
        $fragmentArr = [];
        foreach($collection as $model){
            array_push($fragmentArr, $model->fragment->start . '-' . $model->fragment->end);
        }

        foreach($fragmentArr as $timeFragment) {
            // 获取该时段下已被预订的裁判列表
            $ban = $this->getBookingReferees($date, $timeFragment);
            if(count($ban) > 0) {
                foreach($ban as $banReferee) {
                    // 若已被预订的裁判存在于该订单要预订的裁判列表里，则抛出错误
                    if(in_array($banReferee['referee_id'], $refereeIds)) {
                        throwError('您预订的裁判已在其他场地的重合时段里被预订了，请您重新预订');
                    }
                }

            }
        }

    }

    public function bookArena($date, $part_time_fragment_ids, $refereeIds)
    {
        // 获取用户id
        $user = UserService::getCurrentLoginUser(true);

        // 查找所有裁判
        $refereeList = (new RefereeLogic)->getRefereesById($refereeIds);
        // 计算所有裁判的总费用
        $totalRefereePrice = array_reduce($refereeList->toArray(), function($total, $referee) {
            return $total += $referee['price'];
        }, 0);

        // 订单总价
        $total = $totalRefereePrice;
        $collection = (new ArenaPartTimeFragmentModel())->getInfo(
            $part_time_fragment_ids
        );
        // 检查该日期下的这些时段是否有裁判已被预订，且和该订单要预订的裁判是否有相同的，有则抛出错误
        $this->hasSameRefere($date, $collection, $refereeIds);

        $orderFragments = [];
        foreach ($collection as $model) {
            // 判断该场地和该半场的状态是否为开启
            $this->canBookArenaAndPart($model->id);
            // 该场次的实际价格
            $price =
                $this->getFragmentPrice($date, $model->id) ?? $model->price;
            // 累计总价
            $total += $price;
            array_push($orderFragments, [
                'part_time_fragment_id' => $model->id,
                // 将该场次的状态设为关闭
                'status' => config('arena.status.close'),
                // 记录该场次的默认价格
                'price' => $price,
                'arena_id' => $model->part->arena->arena_id,
                'part_id' => $model->arena_part_id,
                'fragment_id' => $model->time_fragment_id,
                'book' => config('arena.book.user'),
                'user_id' => $user->user_id,
                'start_date' => $date,
                'end_date' => $date,
                // 该场地的默认最大限制
                'maximum' => $model->part->arena->maximum,
                // 该场地的默认最少限制
                'minimum' => $model->part->arena->minimum,
                // 该半场的默认连续限制
                'continuity' => $model->part->continuity,
                'time_fragment' =>
                    $model->fragment->start . '-' . $model->fragment->end,
                'arena' => $model->part->arena->name,
                'arena_part' => $model->part->name,
            ]);
        }

        // 添加订单记录
        $order = (new BookingModel())->order(
            $user->user_id,
            $date,
            $total,
            $orderFragments,
            $refereeList
        );
        $notifyUrl =
            Request::domain() . '/index.php/mxty/payment/bookingNotify';
        // 统一下单API
        $wxConfig = static::getWxConfig();
        $WxPay = new WxPay($wxConfig);
        $result = $WxPay->unifiedorder(
            $order->order_no,
            $user->open_id,
            $total,
            OrderTypeEnum::ORDER,
            $notifyUrl
        );
        return array_merge($result, ['appId' => $wxConfig['app_id']]);
    }

    /**
     * 获取微信支付配置
     * @return array
     * @throws BaseException
     * @throws \think\db\exception\DataNotFoundException
     * @throws \think\db\exception\DbException
     * @throws \think\db\exception\ModelNotFoundException
     */
    private static function getWxConfig()
    {
        return WxappModel::getWxappCache(getStoreId());
    }

    public function getFragmentPrice($date, $part_time_fragment_id)
    {
        // 查询该时段是否有未到期的管理员操作记录(取最新操作时间的那一条为准)，如果有则获取操作记录中的价格作为实际价格
        $bookingFragment = (new BookingFragmentModel())->getPrice(
            $date,
            $part_time_fragment_id
        );
        if (!empty($bookingFragment)) {
            // 判断状态是否关闭
            if ($bookingFragment->status == config('arena.status.close')) {
                // 如果是管理员的操作记录, 或者是前台预订且已付款的记录
                $hasPaid =
                    $bookingFragment->book == 1 &&
                    $bookingFragment->pay_status == OrderPayStatusEnum::SUCCESS;
                if ($bookingFragment->book == 0 || $hasPaid) {
                    throwError(
                        "{$bookingFragment->arena}-{$bookingFragment->arena_part} {$bookingFragment->time_fragment}场次已被预约，请重新选择"
                    );
                }
            }
            $price = $bookingFragment->price;
        }
        return $price ?? null;
    }

    // 查询场地和半场的默认状态是否关闭
    public function canBookArenaAndPart($part_time_fragment_id)
    {
        $status = (new ArenaPartTimeFragmentModel())->getArenaAndPartStatus(
            $part_time_fragment_id
        );
        $canBook = !in_array(config('arena.status.close'), $status);
        if (!$canBook) {
            throwError('场地或半场已经关闭，请重新选择');
        }
    }

    // 获取订场二维码信息
    public function checkBooking($bookingFragmentId, $codeToken)
    {
        $this->verifyUserInfo();
        // 验证二维码所属的用户的身份
        $user = $this->getUserInfo($codeToken);
        if (!$user) {
            throwError('二维码已使用或已失效，请刷新会员卡二维码后重新扫码');
        }

        $booking = BookingFragmentModel::getBookingInfo(
            $bookingFragmentId,
            $user->user_id
        );
        if ($booking->isEmpty()) {
            throwError('没有对应订场信息');
        }

        //判断订单信息是否已过期
        $booking->isExpired = $this->isExpired($booking);
        // 添加上用户的姓名和手机号
        $booking->real_name = $user->real_name;
        $booking->mobile = $user->mobile;

        return $booking->toArray();
    }

    // 确认用户已到场
    public function present($bookingFragmentId, $codeToken)
    {
        $this->verifyUserInfo();
        // 验证二维码所属的用户的身份
        $user = $this->getUserInfo($codeToken);
        if (!$user) {
            throwError('二维码已使用或已失效，请刷新会员卡二维码后重新扫码');
        }

        $booking = BookingFragmentModel::getBookingInfo(
            $bookingFragmentId,
            $user->user_id
        );

        if($this->isExpired($booking)) {
            throwError('该订场场次已过期');
        }

        if($booking->used == 1) {
            throwError('已经确认过了哦');
        }

        $booking->used = 1;
        $booking->save();
        Cache::delete($codeToken);
    }

    // 检查用户身份
    public function verifyUserInfo()
    {
        // 获取进行扫码操作的用户信息，如果未登陆会提示用户登陆
        $scanCodeUser = UserService::getCurrentLoginUser(true);
        // 验证扫码操作的用户身份，是否为工作人员
        $employee = (new CoachModel())->findEmployeeByUserId(
            $scanCodeUser->user_id
        );
        if ($employee->isEmpty()) {
            throwError('只有工作人员才能进行扫码操作哦');
        }
    }

    // 检查订单信息是否已过期
    public function isExpired($bookingModel)
    {
        $date = $bookingModel->start_date;
        $time = explode('-', $bookingModel->time_fragment)[1];
        $bookingTime = DateTime::createFromFormat(
            'Y-m-d H:i',
            "{$date} {$time}"
        );
        $now = new DateTime();
        $interval = $now->diff($bookingTime);
        // 订单已过期
        if ($interval->invert == 1) {
            return true;
        }

        return false;
    }

    public function getUserInfo($token)
    {
        // 通过该二维码token获取user_id
        $user_id = Cache::get($token);
        // 获取用户信息
        $user = UserModel::detail($user_id);

        return $user;
    }

    // 生成会员卡二维码token
    public function generateCodeToken()
    {
        // 获取当前请求的用户信息
        $user = UserService::getCurrentLoginUser(true);
        $codeToken = (new LoginService())->makeToken($user->user_id);
        // 设置一个5分钟的缓存
        Cache::set($codeToken, $user->user_id, 300);
        return $codeToken;
    }
}
